﻿using System;
using weka.core;
using weka.classifiers;
using weka.filters;
using weka.filters.unsupervised.attribute;

namespace OMC.Classification
{
    /// <summary>
    /// This class contains all about Text Classification with weka.
    /// The imported assemblies of WEKA are generated with IKVM.
    /// </summary>
    /// <remarks>
    /// Source: http://stackoverflow.com/questions/9707825/basic-text-classification-with-weka-in-java
    /// </remarks>
    public class TextClassifier
    {
        private Instances trainingData;
        private Instances filteredTrainingData;
        private Instance testData;
        private Instance filteredTestData;
        private Instances testSet;
        private FastVector classValues;
        private FastVector attributes;
        private StringToWordVector filter;
        private Classifier classifier;
        private bool isClassesBuilt;

        /// <summary>
        /// The constructor of a Textclassifier which starts another constructor.
        /// </summary>
        /// <param name="selectedClassifier">a Classifier such as Naive Bayes..</param>
        public TextClassifier(Classifier selectedClassifier)
            : this(selectedClassifier, 10)
        {
        }

        /// <summary>
        /// The constructor of a TextClassifier.
        /// </summary>
        /// <param name="selectedClassifier">a Classifier such as Naive Bayes..</param>
        /// <param name="startSize">Start size of the FastVector</param>
        private TextClassifier(Classifier selectedClassifier, int startSize)
        {
            isClassesBuilt = false;
            filter = new StringToWordVector();
            classifier = selectedClassifier;

            // Create vector of attributes.
            attributes = new FastVector(2);

            // Add attribute for holding texts.
            attributes.addElement(new weka.core.Attribute("text", (FastVector)null));

            // Add class attribute.
            classValues = new FastVector(startSize);
            trainingData = null;
            filteredTrainingData = null;
            testSet = null;

        }

        /// <summary>
        /// This method sets the Trainings Dataset.
        /// </summary>
        /// <param name="data">the TrainingDataset</param>
        public void SetTrainingSet(Instances data)
        {
            this.trainingData = null;
            this.trainingData = data;
        }

        /// <summary>
        /// This method gets the Class Name of a Class Index
        /// </summary>
        /// <param name="index">the index of a ClassName</param>
        /// <returns>a String of the ClassName</returns>
        public String GetClassName(int index)
        {
            return filteredTestData.classAttribute().value(index);
        }

        /// <summary>
        /// This method adds a class name of classification to the instances.
        /// </summary>
        /// <param name="className">the Classname of a class</param>
        public void AddClassName(String className)
        {
            // if required, double the capacity.
            int capacity = classValues.capacity();
            if (classValues.size() > (capacity - 5))
            {
                classValues.setCapacity(capacity * 2);
            }
            classValues.addElement(className);
        }

        /// <summary>
        /// The method builds the classes of the classification.
        /// </summary>
        public void BuildAfterClassesAdded()
        {
            attributes.addElement(new weka.core.Attribute("class", classValues));
            // Create dataset with initial capacity of 100, and set index of class.
            trainingData = new Instances("OMC", attributes, 100);
            trainingData.setClassIndex(trainingData.numAttributes() - 1);
            isClassesBuilt = true;
        }

        /// <summary>
        /// This method adds mail data to the instances.
        /// </summary>
        /// <param name="mailContent">the mailcontent to add</param>
        /// <param name="className">in which classname the mailcontent is</param>
        public void AddMailData(String mailContent, String className)
        {
            if (!isClassesBuilt)
            {
                throw new Exception("Must use buildAfterClassesAdded first");
            }
            mailContent = mailContent.ToLower();
            //className = className.ToLower();
            // Make message into instance.
            Instance instance = MakeInstance(mailContent, trainingData);
            // Set class value for instance.
            instance.setClassValue(className);
            // Add instance to training data.
            trainingData.add(instance);
        }

        /// <summary>
        /// This method gets the Instances after created.
        /// </summary>
        /// <returns>the Instances of the builded DataSet</returns>
        public Instances GetInstancesAfterMailsAdded()
        {
            return trainingData;
        }

        /// <summary>
        /// This method classifies a message.
        /// </summary>
        /// <param name="message">the message to classify, type of String</param>
        /// <returns>The Result in form of a double array</returns>
        public double[] ClassifyMessage(String message)
        {
            message = message.ToLower();

            // Check whether classifier has been built.
            if (trainingData.numInstances() == 0)
            {
                throw new Exception("No classifier available.");
            }

            //Use Filter for Data
            // Initialize filter and tell it about the input format.
            filter.setInputFormat(trainingData);
            // Generate word counts from the training data.
            filteredTrainingData = Filter.useFilter(trainingData, filter);

            // Rebuild classifier.
            classifier.buildClassifier(filteredTrainingData);
            testSet = trainingData.stringFreeStructure();

            testData = MakeInstance(message, testSet);

            // Filter
            filter.input(testData);
            filteredTestData = filter.output();
            //String ClassName = filteredTestData.classAttribute().value((int)classifier.classifyInstance(filteredTestData));
            //Utils.Debug.print("Klassenname: "+ClassName);
            return classifier.distributionForInstance(filteredTestData);
        }

        /// <summary>
        /// This method makes the Instance.
        /// </summary>
        /// <param name="textRelation">the Arff Data Relation Text</param>
        /// <param name="data">The Data to generate the Instance</param>
        /// <returns></returns>
        private Instance MakeInstance(String textRelation, Instances data)
        {
            // Create instance of length two.
            Instance instance = new Instance(2);
            // Set value for message attribute
            weka.core.Attribute messageAtt = data.attribute("text");
            instance.setValue(messageAtt, messageAtt.addStringValue(textRelation));
            // Give instance access to attribute information from the dataset.
            instance.setDataset(data);
            return instance;
        }
    }
}